/*
 * TornLog.js
 *
 * description: Heavily customizable UI based logging class.
 *				Which enables developers to physically see a log as an alternative to alert and console.
 *				TornLog also utilizes MooTools Log for console based logs, and extends it's functionality.
 *
 * license: MIT-style license.
 *
 * copyright: Copyright (c) 2010-2011 [William Baumbach](http://torntech.com/).
 *
 * requires:	[MooTools 1.2.4 or greater,
 *				 MooTools-More 1.2.4: Drag.Move, Log, HTMLTable.Zebra, HTMLTable.Sort, Date ]
 * usage: var myLog = new TornLog([options]);
 *
 */

var TornLog = new Class({
	'version':	'1.0.1.0',				// Major.Minor.UI.Revision
	'build':	''						// CRC build ID
});

(function(){
/*
 * Protected variables EDIT AT YOUR OWN RISK
 */
	var own, opts, logged, rowID;
	var resizer, dragger;
	var UI = {
		tornLogUI: null,
		tornLogScroller: null,
		tornLogTable: null,
		tornLogMenu: null,
		tornLogDrag: null
	};

	var lineHeaders = {
		'num': {content: 'id', properties: {'class': 'num'}},
		'tm': {content: 'time', properties: {'class': 'tm'}},
		'msg': {content: 'msg', properties: {'class': 'msg'}},
		'dur': {content: 'dur', properties: {'class': 'dur'}}
	};

	var buttons = {
		'status' : new Element('div', {'class': 'button statusE', 'title': 'Disable', events: {'click': function(){own.toggleStatus(this);}}}),
		'clrLog' : new Element('div', {'class': 'button clr', 'title': 'Clear Log', events: {'click': function(){own.clearLog();}}}),
		'killUI' : new Element('div', {'class': 'button ui kill', 'title': 'Close', events: {'click': function(){own.killUI();}}}),
		'hideUI' : new Element('div', {'class': 'button ui hide', 'title': 'Hide', events: {'click': function(){own.toggleUI(this);}}}),
		'lockUI' : new Element('div', {'class': 'button ui lock', 'title': 'Lock', events: {'click': function(){own.toggleLockUI(this);}}})
	};
	
/*
 * Start of TornLog Class
 */
TornLog.implement({
	logged: [],							// Stored messages as an Array

	Implements: [Events, Options, Chain],

	options:{
		// Log settings
		useMooLog: true,				// Enables Usage of MooTools Log
		enabled: true,					// true: enables Log on init, false: myObject.enableLog() must be called to enable logging.

		// Log UI Options
		ui:{
			enabled: true,				// true: draws a UI in the browser, false: disables the UI from being drawn
			placement: 'top',			// Order in which to inject the UI ('top', 'bottom', 'before', 'after')
			style: 'top:1px;right:1px;',// Custom style to load that may not work in css such as right/top/bottom
			movable: true,				// Enable drag to move the UI
			sizable: true,				// Enable drag resizing the UI,
			opacity: 1,					// opacity of the UI - 1: fully visible, 0.x: partially visible, 0: not visible
			clasName: 'tornLog',		// ID name to used to appened to the UI elements
			wrapper: 'div',				// Acceptable block elements EG: p, div, pre, code
			container: false,			// Container for Log UI
			lockoriginalsize: true,		// Forces the minimal size to be the original size
			keepincontainer: true,		// Forces the UI to remain inside of the container. (Disabled in IE)
			minSize: {					// Minimal Size the UI can be resized to
				x: 150,
				y: 100
			}
			/*			Possibly added on a later version
			buttons:{
				'status': true,			// Display toggle Log Stauts button
				'clrLog': true,			// Display Clear Log button
				'killUI': true,			// Display Close UI button (destroys the UI)
				'hideUI': true,			// Display toggle Hide UI button
				'lockUI': true			// Display toggle Lock UI position button
			}
			*/
		},

		// Log Line Options
		line: {
			/*			Possibly added on a later version
			container: 'tr',			// Container for each line
			sects: 'td',				// Container for each section of a line
			headerContainer: 'tr',		// Container for the line header
			headerSects: 'th',			// Container for each section of the line header
			*/
			start: 1,					// Starting Log Number
			numbers: true,				// Show Line Numbers
			dates: true,				// Show line Dates
			dateFormat: '[%m-%d-%y %H:%M:%S]',	// Date/time format
			durations: true,			// Show Line durations in miliseconds
			append: '',					// (string) append to message EG: messgage(string) | Example: message - tornLog
			prepend: ''					// (string) to prepend to message EG: (string)message | Example: tornLog: message
		},

		// Log Events
		onInit: $empty,					// Function to fire when TornLog Starts
		onEnable: $empty,				// Function to fire when logging is enabled
		onDisable: $empty,				// Function to fire when logging is disabled
		onClearLog: $empty,				// Function to fire when clearLog() is called
		onSendLog: $empty,				// Function to fire when log() is called
		onShow: $empty,					// Function to fire when ui is shown
		onHide: $empty					// Function to fire when ui is hidden
	},

	isMooLog: !(typeof(Log) == 'undefined'),

	initialize: function(options){
		own = this;
		own.options.ui.container = document.body;
		own.setOptions(options);
		opts = own.options;
		logged = own.logged;

		own.fireEvent('init');

		rowID = opts.line.start;

		if (opts.useMooLog && own.isMooLog)
			own.MooLog = new Log;

		opts.enabled?
			own.enable():
			own.disable();
	},

	addHeaders: function(){
		if (!$defined(UI.tornLogUI) || !own.created)
			return own.createUI();

		var lineH = {
			'num': opts.line.numbers,
			'tm': opts.line.dates,
			'msg': true,
			'dur': opts.line.durations
		};

		var headersResult = [];
		$each(lineH, function(val, key){
			if (val)
				headersResult.push(lineHeaders[key]);
		});
		return headersResult;
	},

	addButtons: function(){
		var btns = function(){
			var result = []
			$each(buttons, function(val){
				result.push(val);
			});
			return result;
		};
		UI.tornLogMenu.adopt(btns());
	},

	stopEvent: function(e){
		return e.stop();
	},

	createUI: function(){
		own.killUI();
		own.created = true;
		own.originalSize = false;

		UI.tornLogUI = new Element(opts.ui.wrapper, {
			'id': opts.ui.clasName,
			'class': 'wrapper',
			'style': opts.ui.style
		});
/*
				Didn't work for IE but worked great for FF and Safari
		UI.tornLogScrollWrapper = new Element('div', {
			'id': opts.ui.clasName + 'scrollWrapper'
		});
*/
		UI.tornLogScroller = new Element('div', {
			'class'	: 'scroller'
		});

		UI.tornLogTable = new HtmlTable({
			properties:{
				'id': opts.ui.clasName + 'Table',
				border: 0,
				cellspacing: 0,
				cellpadding: 1,
				'class': 'log'
			},
			headers: own.addHeaders()
		});

		UI.tornLogMenu = new Element('div', {
			'id': opts.ui.clasName + 'Menu'
		});

		UI.tornLogDrag = new Element('div', {
			'id': opts.ui.clasName + 'Drag',
			events: {
				'mousedown': function(e){
					e.stopPropagation();
					UI.tornLogUI.addEvent('selectstart', own.stopEvent);
				},
				'mouseup': function(e){
					UI.tornLogUI.removeEvent('selectstart', own.stopEvent);
				},
				'contextmenu': own.stopEvent
			}
		});
		own.addHeaders();
		own.addButtons();
		own.insertUI();
		own.toggleLockUI();
	},

	getContainer: function(){
		return opts.ui.keepincontainer?
					Browser.Engine.trident?
						false:
						opts.ui.container:
					false;
	},

	getOriginalSize: function(el){
		return own.originalSize = !own.originalSize?el.getSize():own.originalSize;
	},

	getScrollerSize: function(){
		return own.scrollOrigSize = !own.scrollOrigSize?UI.tornLogScroller.getSize().y:own.scrollOrigSize;
	},

	toggleStatus: function(el){
		if(!el) el = $$('#'+opts.ui.clasName+' .statusE') || $$('#'+opts.ui.clasName+' .statusD');
		if (el.hasClass('statusE')){
			own.disable();
			el.removeClass('statusE');
			el.addClass('statusD');
			el.setAttribute('title', 'Enable');
		} else {
			own.enable();
			el.removeClass('statusD');
			el.addClass('statusE');
			el.setAttribute('title', 'Disable');
		}
	},

	toggleLockUI: function(el){
		if(!el) el = $$('#'+opts.ui.clasName+' .lock') || $$('#'+opts.ui.clasName+' .unlock');
		if (opts.ui.movable){
			if($chk(el)){
				el.removeClass('unlock');
				el.addClass('lock');
				el.set('title', 'Lock');
				UI.tornLogMenu.setStyle('cursor', 'move');
				UI.tornLogDrag.setStyle('cursor', 'se-resize');
			}
			dragger.attach();
			resizer.attach();
		} else {
			if($chk(el)){
				el.removeClass('lock');
				el.addClass('unlock');
				el.set('title', 'Unlock');
				UI.tornLogMenu.setStyle('cursor', 'default');
				UI.tornLogDrag.setStyle('cursor', 'default');
			}
			dragger.detach();
			resizer.detach();
		}
		return opts.ui.movable = !opts.ui.movable;
	},

	lockSize: function(el){
		var minSize = opts.ui.lockoriginalsize?own.getOriginalSize(el):opts.ui.minSize;
		if (el.getSize().y < minSize.y)
			el.setStyle('height', minSize.y);
		if (el.getSize().x < minSize.x)
			el.setStyle('width', minSize.x);
	},
	
	addSizer: function(){
		own.addDragger();
		own.lockSize(UI.tornLogUI);
	},

	addDragger: function(){
		UI.tornLogScroller.addEvent('selectstart', own.stopEvent);
		UI.tornLogScroller.setStyle('MozUserSelect', 'none');
	},

	addComplete: function(){
		UI.tornLogScroller.removeEvent('selectstart', own.stopEvent);
		UI.tornLogScroller.setStyle('MozUserSelect', 'text');
	},

	insertUI: function(){
		//document.body
		UI.tornLogUI.inject(opts.ui.container, opts.ui.placement).adopt(
			UI.tornLogMenu,
				UI.tornLogScroller.adopt(
					UI.tornLogTable,
					UI.tornLogDrag
				)
		);

		dragger = UI.tornLogUI.makeDraggable({
			container: own.getContainer(),
			handle: $(opts.ui.clasName + 'Menu'),
			onDrag: own.addDragger,
			onComplete: own.addComplete
		});

		resizer = UI.tornLogUI.makeResizable({
			handle: UI.tornLogDrag,
			onDrag: own.addSizer,
			onComplete: own.addComplete
		});
		UI.tornLogUI.set('opacity', opts.ui.opacity);
	},

	killUI: function(){
		if ($chk($(opts.ui.clasName)))
			$(opts.ui.clasName).dispose();

		$each(UI, function(_, key){
				UI[key] = null;
		});
		own.originalSize = false;
		own.created = false;
	},

	showUI: function(){
		if (!$chk($(opts.ui.clasName))) return false;
		if(Fx.Tween){
			UI.tornLogScroller.fade('in');
		} else
			UI.tornLogScroller.setStyle('disply', '');
		return own.fireEvent('show');
	},

	hideUI: function(){
		if (!$chk($(opts.ui.clasName))) return false;
		if(Fx.Tween){
			UI.tornLogScroller.fade('out');
		} else
			UI.tornLogScroller.setStyle('display', 'none');
		return own.fireEvent('hide');
	},

	toggleUI: function(el){
		if(!own.uiHidden){
			if($chk($(el))){
				el.removeClass('hide');
				el.addClass('show');
				el.setAttribute('title', 'Show');
			}
			own.hideUI();
		} else {
			if($chk($(el))){
				el.removeClass('show');
				el.addClass('hide');
				el.setAttribute('title', 'Hide');
			}
			own.showUI();
		}
		return own.uiHidden = !own.uiHidden;
	},

	disable: function(killUI){
		own.fireEvent('disable');
		if (opts.useMooLog && own.isMooLog)
			own.MooLog.disableLog();
		if (opts.ui.enabled && killUI)
			own.killUI();

		own.send = own.disabled;
		own.send('Logging disabled');
	},

	enable: function(){
		own.fireEvent('enable');
		if (opts.useMooLog && own.isMooLog)
			own.MooLog.enableLog();

			if (opts.ui.enabled)
				addEvent('domready', function(){own.createUI()});

		own.send = own.enabled;
		logged.each(function(args){
			own.send.apply(own, args);
		});
		return own.clearLog(true);
	},

	disabled: function(){
		logged.push(arguments);
		return this;
	},

	enabled: function(msg, prefire, callback){
		own.curTime = own.getDate();
		own.fireEvent('sendLog');
		msg = opts.line.prepend + msg + opts.line.append;

		typeof(prefire) == 'function'?
			own.preFire(msg, prefire):
			own.addMsg(msg);

		if (typeof(callback) == 'function')
			callback.run();
	},

	preFire: function(msg, prefire){
		return own.chain(
			prefire.run(),
			own.addMsg(msg)
		);
	},

	addMsg: function(msg){
		if (opts.useMooLog && own.isMooLog && msg)
				own.MooLog.log(msg);
		if (!opts.ui.enabled || !$chk($(opts.ui.clasName))) return false;
		var cols = {
			'num': {content: rowID++ + ')', properties: {'class': 'num'}},
			'tm': {content: own.formatDate(), properties: {'class': 'tm'}},
			'msg': {content: msg, properties: {'class': 'msg'}},
			'dur': {content: own.duration(own.curTime) + ' ms', properties: {'class': 'dur'}}
		};

		var lineOpts = {
			'num': opts.line.numbers,
			'tm': opts.line.dates,
			'msg': true,
			'dur': opts.line.durations
		};

		var rows = [];
		$each(lineOpts, function(val, key){
			if (val)
				rows.push(cols[key])
		});
		
		UI.tornLogTable.push(rows, {'class':'line'});
		if (Fx.Scroll && !own.fxScroll)
			own.fxScroll = new Fx.Scroll(UI.tornLogScroller);
		else if(Fx.Scroll)
			own.fxScroll.toBottom();
	},

	clearLog: function(local){
		if (!local){
			own.fireEvent('clearLog');
			rowID = opts.line.start;
			UI.tornLogTable.empty();
		}

		logged.empty();
		return own.send('Log initialized');
	},

	getDate: function(){
		return new Date();
	},

	duration: function(time){
		return !time?0:(own.getDate() - time);
	},

	formatDate: function(date){
		if (!date)
			date = own.getDate();

		return date.format(opts.line.dateFormat);
	}
});
})();